import aiofiles
import os
import time
from datetime import datetime, timedelta, timezone
from typing import Annotated, Literal, Union
from uuid import UUID

import jwt
import uvicorn
from fastapi import FastAPI, Path, Query, Body, Cookie, Header, status, Form, File, UploadFile, HTTPException, Request, \
    Depends
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.openapi.docs import get_swagger_ui_html, get_swagger_ui_oauth2_redirect_html, get_redoc_html
from fastapi.responses import HTMLResponse,JSONResponse,PlainTextResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.middleware.cors import CORSMiddleware
from jwt import InvalidTokenError
from passlib.context import CryptContext
from sqlmodel import SQLModel, Field, create_engine, Session, select

from starlette.staticfiles import StaticFiles





# 创建FastAPI实例，禁用掉FastAPI会自动生成Swagger UI和Redoc文档
app = FastAPI(docs_url=None, redoc_url=None)

# 静态文件挂载（路由与静态文件映射）
app.mount("/static", StaticFiles(directory="../statics"), name="static")


# 重新提供一个Swagger UI，相当于我们自己重写了这么一个路由
@app.get("/docs", include_in_schema=False)
def custom_swagger_ui():
    return get_swagger_ui_html(
        openapi_url = "/openapi.json",
        title = "Swagger UI for FastAPI",
        swagger_js_url="/static/swagger-ui-bundle.min.js",
        swagger_css_url="/static/swagger-ui.min.css"
    )

# 这里与Swagger UI一样，重新提供Redoc
@app.get("/redoc", include_in_schema=False)
def custom_redoc():
    return get_redoc_html(
        openapi_url = "/openapi.json",
        title = "Redoc for FastAPI",
        redoc_js_url="/static/redoc.standalone.js"
    )

# 模型基类
class EmpBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)

# 表模型，对应数据库表
class Emp(EmpBase, table=True):
    # __tablename__ = "emp"
    __table_args__ = {'extend_existing': True}
    id: int | None = Field(default=None, primary_key=True)
    post: str

# 公开模型：返回给客户端的
class EmpPublic(EmpBase):
    id: int

# 创建数据模型
class EmpCreate(EmpBase):
    post: str

# 更新数据模型
class EmpUpdate(EmpBase):
    name: str | None = None
    age: int | None = None
    post: str | None = None



# 数据库连接
DATABASE_URL = r"mysql+pymysql://root:A7#mZ9!pL3$q@localhost:3306/test"
# # 这进而配置表示在不同线程中使用同一个连接（mysql不支持下面这个配置）
# connect_args = {"check_same_thread": False}
# 创建引擎
engine = create_engine(DATABASE_URL, echo=True)

# 创建数据库连接会话的依赖项
def get_db_session():
    with Session(engine) as session:
        yield session

# 定义一个依赖项，方便后面使用
SessionDep = Annotated[Session, Depends(get_db_session)]

# 创建表
SQLModel.metadata.create_all(engine)


@app.post("/emp",response_model=EmpPublic, status_code=201)
async def create_emp(emp: EmpCreate, db_session: SessionDep):
    # 使用Emp模型对传进来的emp对象进行验证和转换，生成一个Emp类型的实例db_emp
    db_emp = Emp.model_validate(emp)
    db_session.add(db_emp)
    db_session.commit()
    db_session.refresh(db_emp)
    return db_emp


@app.get("/emps",response_model=list[EmpPublic])
async def read_emps(db_session: SessionDep,page_index: Annotated[int , Query(ge=1)] = 1, page_size: Annotated[int, Query(ge=0, le=100)] = 10):
    offset = (page_index - 1) * page_size
    limit = page_size
    emps = db_session.exec(select(Emp).offset(offset).limit(limit)).all()
    return emps

@app.get("/emp/{emp_id}",response_model=EmpPublic)
async def read_emp(emp_id: int, db_session: SessionDep):
    emp = db_session.get(Emp, emp_id)
    if emp is None:
        raise HTTPException(status_code=404, detail=f"id为{emp_id}的员工不存在！")
    return emp


def check_emp_exists(id:int,db_session: Session,type: str |  None = "dml"):
    """
    检查指定id的数据在数据库中是否存在
    :param id: id
    :param db_session: 数据库会话session
    :param type: 操作类型,dml表示需要with_for_update(),其它情况不需要
    :return: 数据库中查询的数据，如果查不到会抛异常404
    """
    if type == "dml":
        # with_for_update()会对查询到的记录加锁，直到提交或回滚事务（session.commit()/session.rollback()）
        query = select(Emp).where(Emp.id == id).with_for_update()   # 这里使用with_for_update()，确保在更新时数据不会被其他事务修改
    else:
        query = select(Emp).where(Emp.id == id)

    # 由于只会有一条记录所以这里使用first()
    record = db_session.exec(query).first()
    if not record:
        raise HTTPException(status_code=404, detail=f"Emp中id为{id}的记录不存在!")

    return record


@app.delete("/emp/{emp_id}")
async def delete_emp(emp_id: int, db_session: SessionDep):
    # 查询一下这指定id的员工是否存在
    # 使用行级锁来防止事务问题
    emp = check_emp_exists(emp_id, db_session, "dml")
    # emp = db_session.get(Emp, emp_id)
    if not emp:
        raise HTTPException(status_code=404, detail=f"id为{emp_id}的员工不存在！")
    db_session.delete(emp)
    db_session.commit() # 提交记录
    return {"message": "删除成功"}


def set_attrs(obj, data:dict):
    """
    修改属性
    :param obj: 要修改哪一个属性中的数据
    :param data: 要修改的是哪些数据 是一个dict
    :return:
    """
    for key, value in data.items():
        # 修改哪一个对象obj中的指定属性key的值为value
        if hasattr(obj, key) and getattr(obj, key) is not None and key != "id" :
            setattr(obj, key, value)

@app.put("/emp/{emp_id}", response_model=EmpPublic)
async def updateEmp(emp_id: int, emp: EmpUpdate, db_session: Session = Depends(get_db_session)):
    emp_db = db_session.get(Emp, emp_id)
    if not emp_db:
        raise HTTPException(status_code=404, detail=f"id为{emp_id}的员工不存在")
    emp_data = emp.model_dump(exclude_unset=True)
    # 把emp_data中的数据更新到emp_db中，但是不包含未设置的字段
    emp_db.sqlmodel_update(emp_data)
    db_session.add(emp_db)
    db_session.commit()
    db_session.refresh(emp_db)
    return emp_db

if __name__ == '__main__':
    app.debug = True
    uvicorn.run("main:app", host="127.0.0.1", port=8029, reload=True)

